#pragma once

#include <hiredis/async.h>
#include <hiredis/hiredis.h>

#include <string_view>

#include "zrpc/base/logger.h"
#include "zrpc/net/event_loop.h"
#include "zrpc/net/tcp_client.h"
#include "zrpc/net/tcp_connection.h"

struct redisAsyncContext;

class HiredisClient : public std::enable_shared_from_this<HiredisClient> {
 public:
  typedef std::function<void(HiredisClient *, int)> ConnectCallback;
  typedef std::function<void(HiredisClient *, int)> DisconnectCallback;
  typedef std::function<void(HiredisClient *, redisReply *)> CommandCallback;

  HiredisClient(zrpc::EventLoop *loop, const std::string &ip, uint16_t port);

  ~HiredisClient();

  bool Connected() const;

  const char *Errstr() const;

  void SetConnectCallback(const ConnectCallback &cb) { connect_cb_ = cb; }

  void SetDisconnectCallback(const DisconnectCallback &cb) {
    disconnect_cb_ = cb;
  }

  void Connect();

  void Disconnect();

  int Command(const CommandCallback &cb, std::string_view cmd, ...);

  int Ping();

 private:
  void HandleRead();

  void HandleWrite();

  int GetFd() const;

  void SetChannel();

  void RemoveChannel();

  void ReplyConnectCallback(int status);
  void ReplyDisconnectCallback(int status);
  void ReplyCommandCallback(redisReply *reply, CommandCallback *cb);

  static HiredisClient *GetHiredis(const redisAsyncContext *ac);
  static void AddRead(void *privdata);
  static void DelRead(void *privdata);
  static void AddWrite(void *privdata);
  static void DelWrite(void *privdata);
  static void Cleanup(void *privdata);

  void PingCallback(HiredisClient *msg, redisReply *reply);

  static void RedisConnectCallback(const redisAsyncContext *ac, int status);
  static void RedisDisConnectCallback(const redisAsyncContext *ac, int status);
  static void RedisCommandCallback(redisAsyncContext *ac, void *, void *);

 private:
  zrpc::EventLoop *loop_;
  std::string ip_;
  uint16_t port_;
  redisAsyncContext *context_;
  std::shared_ptr<zrpc::Channel> channel_;
  ConnectCallback connect_cb_;
  DisconnectCallback disconnect_cb_;
};
